import { once } from 'lodash-es'
import { AcornCodeKey, AcornNodePri, Awaitable, CompluginTransformContext, TransformResult } from '../../types'
import { isAst, isPromiseLike } from '../../utils'
import { excludeLoadersOnPitch, getPluginContextFromLoader, getTransformContext, handleTransformResult } from './utils'

const transformLoader: import('webpack').LoaderDefinition = function (originalCode, originalSourcemap, meta) {
  const pluginContext = getPluginContextFromLoader(this)
  if (!pluginContext) {
    this.emitWarning(new Error(`PluginContext not found. If 'thread-loader' is configured, please delete it.`))
    this.callback(null, originalCode, originalSourcemap)
    return
  }

  const injected = this._compiler!.$compluginInjected
  const _module = this._module
  const { transform } = pluginContext
  const resource = _module?.resource ?? this.resource
  const id = injected.virtualResourceToRequest(resource) ?? resource
  const context = getTransformContext(this, originalCode, id, resource, pluginContext)
  const ast = meta?.webpackAST
  let result: Awaitable<TransformResult>

  Object.defineProperty(context, 'isEntry', {
    enumerable: true,
    configurable: true,
    get: once(() => _module && injected.isEntry(_module))
  })

  try {
    result = transform!.call(
      context as any as CompluginTransformContext,
      originalCode ?? '',
      id,
      isAst(ast) && (ast as AcornNodePri)[AcornCodeKey] === originalCode ? ast : undefined
    )
  } catch (error) {
    return this.callback(error instanceof Error ? error : new Error(error as any))
  }

  if (isPromiseLike(result)) {
    this.async()
    Promise.resolve(result).then(
      result => handleTransformResult(originalCode, originalSourcemap as any, result, this),
      error => this.callback(error instanceof Error ? error : new Error(error as any))
    )
  } else {
    handleTransformResult(originalCode, originalSourcemap as any, result, this)
  }
}

const pitch: import('webpack').LoaderDefinition['pitch'] = (transformLoader.pitch = function () {
  excludeLoadersOnPitch(this, transformLoader)
})

export { transformLoader as default, pitch }
